/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
/* Copyright (c) 2021 Hengqi Chen */

#ifndef __CORE_FIXES_BPF_H
#define __CORE_FIXES_BPF_H

#include <vmlinux.h>
#include <bpf/bpf_core_read.h>

extern __u32 LINUX_KERNEL_VERSION __kconfig;

#ifndef __has_builtin		// Optional of course.
  #define __has_builtin(x) 0	// Compatibility with non-clang compilers.
#endif

/**
 * commit 2f064a59a1 ("sched: Change task_struct::state") changes
 * the name of task_struct::state to task_struct::__state
 * see:
 *     https://github.com/torvalds/linux/commit/2f064a59a1
 */
struct task_struct___o {
	volatile long int state;
} __attribute__((preserve_access_index));

struct task_struct___x {
	unsigned int __state;
} __attribute__((preserve_access_index));

static __always_inline __s64 get_task_state(void *task)
{
	struct task_struct___x *t = task;

	if (bpf_core_field_exists(t->__state))
		return BPF_CORE_READ(t, __state);
	return BPF_CORE_READ((struct task_struct___o *)task, state);
}

/**
 * commit 309dca309fc3 ("block: store a block_device pointer in struct bio")
 * adds a new member bi_bdev which is a pointer to struct block_device
 * see:
 *     https://github.com/torvalds/linux/commit/309dca309fc3
 */
struct bio___o {
	struct gendisk *bi_disk;
} __attribute__((preserve_access_index));

struct bio___x {
	struct block_device *bi_bdev;
} __attribute__((preserve_access_index));

static __always_inline struct gendisk *get_gendisk(void *bio)
{
	struct bio___x *b = bio;

	if (bpf_core_field_exists(b->bi_bdev))
		return BPF_CORE_READ(b, bi_bdev, bd_disk);
	return BPF_CORE_READ((struct bio___o *)bio, bi_disk);
}

/**
 * commit d5869fdc189f ("block: introduce block_rq_error tracepoint")
 * adds a new tracepoint block_rq_error and it shares the same arguments
 * with tracepoint block_rq_complete. As a result, the kernel BTF now has
 * a `struct trace_event_raw_block_rq_completion` instead of
 * `struct trace_event_raw_block_rq_complete`.
 * see:
 *     https://github.com/torvalds/linux/commit/d5869fdc189f
 */
struct trace_event_raw_block_rq_complete___x {
	dev_t dev;
	sector_t sector;
	unsigned int nr_sector;
} __attribute__((preserve_access_index));

struct trace_event_raw_block_rq_completion___x {
	dev_t dev;
	sector_t sector;
	unsigned int nr_sector;
} __attribute__((preserve_access_index));

static __always_inline bool has_block_rq_completion()
{
#if __has_builtin(__builtin_preserve_type_info)
	if (bpf_core_type_exists(struct trace_event_raw_block_rq_completion___x))
		return true;
#endif
	if (LINUX_KERNEL_VERSION >= KERNEL_VERSION(5, 18, 0))
		return true;

	return false;
}

/**
 * commit d152c682f03c ("block: add an explicit ->disk backpointer to the
 * request_queue") and commit f3fa33acca9f ("block: remove the ->rq_disk
 * field in struct request") make some changes to `struct request` and
 * `struct request_queue`. Now, to get the `struct gendisk *` field in a CO-RE
 * way, we need both `struct request` and `struct request_queue`.
 * see:
 *     https://github.com/torvalds/linux/commit/d152c682f03c
 *     https://github.com/torvalds/linux/commit/f3fa33acca9f
 */
struct request_queue___x {
	struct gendisk *disk;
} __attribute__((preserve_access_index));

struct request___x {
	struct request_queue___x *q;
	struct gendisk *rq_disk;
} __attribute__((preserve_access_index));

static __always_inline struct gendisk *get_disk(void *request)
{
	struct request___x *r = request;

	if (bpf_core_field_exists(r->rq_disk))
		return BPF_CORE_READ(r, rq_disk);
	return BPF_CORE_READ(r, q, disk);
}

/**
 * commit 6521f8917082("namei: prepare for idmapped mounts") add `struct
 * user_namespace *mnt_userns` as vfs_create() and vfs_unlink() first argument.
 * At the same time, struct renamedata {} add `struct user_namespace
 * *old_mnt_userns` item. Now, to kprobe vfs_create()/vfs_unlink() in a CO-RE
 * way, determine whether there is a `old_mnt_userns` field for `struct
 * renamedata` to decide which input parameter of the vfs_create() to use as
 * `dentry`.
 * commit abf08576afe3("fs: port vfs_*() helpers to struct mnt_idmap") use
 * `struct mnt_idmap *new_mnt_idmap` instead of `struct user_namespace *
 * old_mnt_userns`.
 * see:
 *     https://github.com/torvalds/linux/commit/6521f8917082
 *     https://github.com/torvalds/linux/commit/abf08576afe3
 */
struct renamedata___x {
	struct user_namespace *old_mnt_userns;
	struct new_mnt_idmap *new_mnt_idmap;
} __attribute__((preserve_access_index));

static __always_inline bool renamedata_has_old_mnt_userns_field(void)
{
	if (bpf_core_field_exists(struct renamedata___x, old_mnt_userns))
		return true;
	return false;
}

static __always_inline bool renamedata_has_new_mnt_idmap_field(void)
{
	if (bpf_core_field_exists(struct renamedata___x, new_mnt_idmap))
		return true;
	return false;
}

/**
 * commit 8cd54c1c8480 ("iov_iter: separate direction from flavour")
 * Instead of having them mixed in iter->type, use separate ->iter_type
 * and ->data_source (u8 and bool resp.)
 * see:
 *     https://github.com/torvalds/linux/commit/8cd54c1c8480
 */
struct iov_iter___o {
	unsigned int type;
} __attribute__((preserve_access_index));

/* commit fcb14cb1bdac ("new iov_iter flavour - ITER_UBUF")
 * implement new iov_iter flavour ITER_UBUF
 * see:
 *	https://github.com/torvalds/linux/commit/fcb14cb1bdac
 */
struct iov_iter___x {
	u8 iter_type;
	/* commit fcb14cb1bdac ("new iov_iter flavour - ITER_UBUF")
	 * implement new iov_iter flavour ITER_UBUF
	 */
	void *ubuf;
} __attribute__((preserve_access_index));

static __always_inline bool iov_iter_has_iter_type(void)
{
	if (bpf_core_field_exists(struct iov_iter___x, iter_type))
		return true;
	return false;
}

/**
 * commit 3544de8ee6e4("mm, tracing: record slab name for kmem_cache_free()")
 * replaces `trace_event_raw_kmem_free` with `trace_event_raw_kfree` and adds
 * `tracepoint_kmem_cache_free` to enhance the information recorded for
 * `kmem_cache_free`.
 * see:
 *     https://github.com/torvalds/linux/commit/3544de8ee6e4
 */
struct trace_event_raw_kmem_free___x {
	const void *ptr;
} __attribute__((preserve_access_index));

struct trace_event_raw_kfree___x {
	const void *ptr;
} __attribute__((preserve_access_index));

struct trace_event_raw_kmem_cache_free___x {
	const void *ptr;
} __attribute__((preserve_access_index));

static __always_inline bool has_kfree()
{
#if __has_builtin(__builtin_preserve_type_info)
	if (bpf_core_type_exists(struct trace_event_raw_kfree___x))
		return true;
#endif
	if (LINUX_KERNEL_VERSION >= KERNEL_VERSION(5, 12, 0))
		return true;

	return false;
}

static __always_inline bool has_kmem_cache_free()
{
#if __has_builtin(__builtin_preserve_type_info)
	if (bpf_core_type_exists(struct trace_event_raw_kmem_cache_free___x))
		return true;
#endif
	if (LINUX_KERNEL_VERSION >= KERNEL_VERSION(5, 12, 0))
		return true;

	return false;
}

/**
 * commit 2c1d697fb8ba ("mm/slab_common: drop kmem_alloc & avoid dereferencing
 * fields when not using") Drop kmem_alloc event class, and define kmalloc and
 * kmem_cache_alloc using TRACE_EVENT() macro.
 */
struct trace_event_raw_kmem_alloc___x {
	const void *ptr;
	size_t bytes_alloc;
} __attribute__((preserve_access_index));

struct trace_event_raw_kmalloc___x {
	const void *ptr;
	size_t bytes_alloc;
} __attribute__((preserve_access_index));

struct trace_event_raw_kmem_cache_alloc___x {
	const void *ptr;
	size_t bytes_alloc;
} __attribute__((preserve_access_index));

static __always_inline bool has_kmalloc()
{
#if __has_builtin(__builtin_preserve_type_info)
	if (bpf_core_type_exists(struct trace_event_raw_kmalloc___x))
		return true;
#endif
	if (LINUX_KERNEL_VERSION >= KERNEL_VERSION(6, 1, 0))
		return true;

	return false;
}

static __always_inline bool has_kmem_cache_alloc()
{
#if __has_builtin(__builtin_preserve_type_info)
	if (bpf_core_type_exists(struct trace_event_raw_kmem_cache_alloc___x))
		return true;
#endif
	if (LINUX_KERNEL_VERSION >= KERNEL_VERSION(6, 1, 0))
		return true;

	return false;
}

/**
 * commit 11e9734bcb6a ("mm/slab_common: unify NUMA and UMA version of
 * tracepoints") Drop kmem_alloc event class, rename kmem_alloc_node to
 * kmem_alloc, and remove _node postfix for NUMA version of tracepoints.
 */
struct trace_event_raw_kmem_alloc_node___o {
	const void *ptr;
	size_t bytes_alloc;
} __attribute__((preserve_access_index));

static __always_inline bool has_kmem_alloc_node()
{
#if __has_builtin(__builtin_preserve_type_info)
        if (bpf_core_type_exists(struct trace_event_raw_kmem_alloc_node___o))
		return true;
#endif
        if (LINUX_KERNEL_VERSION < KERNEL_VERSION(6, 1, 0))
		return true;

	return false;
}

/**
 * The bpf_get_socket_cookie helper is landed since kernel v4.12，
 * but only available for tracing programs since kernel v5.12
 * via commit c5dbb89fc2ac("bpf: Expose bpf_get_socket_cookie to tracing programs").
 * Since the helper is used to provide a unique socket identifier,
 * we could use the sock itself as the identifier if the helper is not available.
 * Here, we use BPF_FUNC_check_mtu to check the availability of the helper
 * since they are both introduced in v5.12.
 *
 * see:
 *    https://github.com/torvalds/linux/commit/91b8270f2a4d
 *    https://github.com/torvalds/linux/commit/c5dbb89fc2ac
 *    https://github.com/torvalds/linux/commit/34b2021cc616
 */
static __always_inline __u64 get_sock_ident(struct sock *sk)
{
#if __has_builtin(__builtin_preserve_enum_value)
	if (bpf_core_enum_value_exists(enum bpf_func_id, BPF_FUNC_check_mtu)) {
		return bpf_get_socket_cookie(sk);
	}
#endif
	if (LINUX_KERNEL_VERSION >= KERNEL_VERSION(5, 12, 0))
		return bpf_get_socket_cookie(sk);

	return (__u64)sk;
}

#endif /* __CORE_FIXES_BPF_H */
